package com.i72.freeway;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.util.ParameterizedTypeImpl;
import com.i72.basic.SOAServiceCenter;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.conn.ConnectTimeoutException;
import org.slf4j.MDC;
import org.springframework.beans.BeanUtils;
import org.springframework.util.StopWatch;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Type;
import java.net.SocketTimeoutException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * @author jiangj
 * @version 1.0.0
 * @ClassName HttpHandler.java
 * @Description TODO
 * @createTime 2021年12月28日 16:01:00
 */
@Slf4j
public class HttpHandler {

    private static final byte[] lock = new byte[0];

    private static ConcurrentMap<Type, Type> classTypeCache = new ConcurrentHashMap<>(64, 0.75f, 1);

    //private static List<HandlerInterceptor> handlerInterceptors;

    public static String getServiceAddress(HandleParam handleParam) {

        SOAServiceCenter soaServiceCenter = SpringContextHelper.getApplicationContext().getBean(SOAServiceCenter.class);

        String serviceAddress = soaServiceCenter.getServiceAddress(handleParam.getServiceName());
        if (StringHelper.isNullOrEmpty(serviceAddress)) {
            /*
            ServiceException serviceException = new ServiceException(MDC.get(Constant.REQ_ID), handleParam.getServiceName(),
                    ErrorCodeEnum.UNKNOWN_SERVICE.getCode(), ErrorCodeEnum.UNKNOWN_SERVICE.getErrorMsg());
            log.error(serviceException.getMessage(), serviceException);
            throw serviceException;*/
            throw new RuntimeException("服务不存在");
        }
        return serviceAddress;
    }

    public static Type getType(Type type) {
        Type beforeType = classTypeCache.get(type);
        if (beforeType != null) {
            return beforeType;
        }

        List<Type> types = new ArrayList<>();
        types.add(ResultWrap.class);
        types.add(type);
        if (types.size() != 0) {
            // 支持多级泛型的解析
            for (int i = types.size() - 1; i > 0; i--) {
                beforeType = new ParameterizedTypeImpl(new Type[] { beforeType == null ? types.get(i) : beforeType }, null, types.get(i - 1));
            }
            classTypeCache.put(type, beforeType);
        }
        return beforeType;
    }

    public static String buildUrl(String... str) {
        StringBuilder builder = new StringBuilder();
        for (String s : str) {
            if (s == null) {
                continue;
            }
            builder.append(s);
        }
        return builder.toString();
    }

    public Object handle(HandleParam handleParam, Object[] args, Class returnType) {
        //return handle(handleParam, args, returnType, HttpContext.getFreewayContextMapForDeliver());
        return handle(handleParam, args, returnType, getHeaders());
    }

    public Object handle(HandleParam handleParam, Object[] args, Class returnType, Map<String, String> headerMap) {
        String serviceAddress = getServiceAddress(handleParam);

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Object resultObj = null;
        String url = null;
        String params = "";
        String paramsLog;
        Exception exception = null;
        String result = null;

        if(args!=null && args.length>0){
        //if (ArrayHelper.isNotEmpty(args)) {
            if (args.length == 1) {
                params = JSON.toJSONString(args[0]);
            } else {
                params = JSON.toJSONString(args, SerializerFeature.DisableCircularReferenceDetect);
            }
        }
        /*
        if (SensitiveUtils.isSensitive(handleParam.getMethodKey(), SensitiveUtils.PARAMS, args)) {
            paramsLog = getParamLog(args);
        } else {
            paramsLog = params;
        }*/
        paramsLog = params;
        HandlerContext handlerContext = new HandlerContext();
        BeanUtils.copyProperties(handleParam, handlerContext);
        if(headerMap!=null && !headerMap.containsKey("sysCode")){
            headerMap.put("sysCode",SOAServiceCenter.applicationName);
        }
        headerMap.remove("content-length");
        handlerContext.setHeaders(headerMap);

        Map<String,String> callContextMap = FreewayCallContext.getCallContext();
        if(callContextMap!=null){
            headerMap.putAll(callContextMap);
            FreewayCallContext.removeAll();
        }

        handlerContext.setServiceAddress(serviceAddress);

        try {
            /*
            for (HandlerInterceptor handlerInterceptor : getHandlerInterceptors()) {
                handlerInterceptor.preHandle(args, handlerContext);
            }*/

            String newServiceAddress = handlerContext.getServiceAddress();

            if (HttpHelper.isHttp(newServiceAddress)) {
                url = buildUrl(StringHelper.URLDecode(newServiceAddress), "/api/", handlerContext.getServicePath(), handlerContext.getVersion());
            } else {
                url = buildUrl("http://", newServiceAddress, "/api/", handlerContext.getServicePath(), handlerContext.getVersion());
            }

            result = HttpHelper.post(url, params, headerMap, handlerContext.getTimeout());

            if (handleParam.isResultWrap()) {
                if (result == null) throw new Exception("freeway rpc return null");
                resultObj = JSON.parseObject(result, getType(handleParam.getGenericReturnType()));
            } else {
                // 兼容服务节点下线重试情况
                try {
                    resultObj = JSON.parseObject(result, handleParam.getGenericReturnType());
                } catch (JSONException e) {
                    try {
                        resultObj = JSON.parseObject(result, getType(handleParam.getGenericReturnType()));
                    } catch (JSONException e1) {
                        throw e;
                    }
                }
            }

            /*
            for (HandlerInterceptor handlerInterceptor : getHandlerInterceptors()) {
                handlerInterceptor.postHandle(args, resultObj, handlerContext);
            }*/

            if (resultObj instanceof ResultWrap) {
                ResultWrap resultWrap = (ResultWrap) resultObj;
                if (resultWrap.isSuccess()) {
                    if (Void.TYPE.equals(returnType)) {
                        resultObj = null;
                    } else {
                        resultObj = resultWrap.getResult();
                    }
                } else {
                    if(resultWrap.getErrorCode() != null && resultWrap.getErrorCode() == -9
                    //if (resultWrap.getErrorCode() != null && resultWrap.getErrorCode() == ErrorCodeEnum.SERVICE_DOWN.getCode()
                            && serviceAddress.equals(newServiceAddress)) {
                        // 服务下线，重试其他节点，重试2次
                        log.info("服务节点[{}]下线，重试其他节点", serviceAddress);
                        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
                        if (stackTraceElements[2].getMethodName().equals(stackTraceElements[3].getMethodName())
                                && stackTraceElements[2].getLineNumber() == stackTraceElements[3].getLineNumber()) {
                            log.error("服务节点下线，已重试其他节点2次未成功");
                            throw new RuntimeException("服务节点下线，已重试其他节点2次未成功");
                            /*
                            throw new ServiceException(headerMap.get(Constant.REQ_ID), handlerContext.getServiceName(),
                                    ErrorCodeEnum.PROTOCOL_EXCEPTION.getCode(), ErrorCodeEnum.PROTOCOL_EXCEPTION.getErrorMsg());*/
                        }
                        resultObj = handle(handleParam, args, returnType, headerMap);
                    } else {
                        throw new RuntimeException(resultWrap.getErrorMessage());
                        /*
                        throw new ServiceException(headerMap.get(Constant.REQ_ID), handlerContext.getServiceName(), resultWrap.getErrorCode(),
                                resultWrap.getErrorMessage());*/
                    }
                }
            }

            /*
            if (RpcLogConfig.getConfig().isConsumerLog()) {
                log(true, stopWatch, handlerContext.getServiceName(), url, headerMap, paramsLog, getResultLog(handleParam, result, resultObj));
            }*/
        //} catch (ServiceException e) {
            //exception = e;
            //log(false, stopWatch, handlerContext.getServiceName(), url, headerMap, paramsLog, getResultLog(handleParam, result, resultObj));
            //throw e;
        } catch (ConnectTimeoutException | SocketTimeoutException e) {
            exception = e;
            logError(stopWatch, handlerContext.getServiceName(), url, headerMap, paramsLog, null, e);
            throw new RuntimeException("RPC网络异常");
            /*
            throw new ServiceException(headerMap.get(Constant.REQ_ID), handlerContext.getServiceName(), ErrorCodeEnum.TIMEOUT_EXCEPTION.getCode(),
                    ErrorCodeEnum.TIMEOUT_EXCEPTION.getErrorMsg());*/
        } catch (Exception e) {
            exception = e;
            logError(stopWatch, handlerContext.getServiceName(), url, headerMap, paramsLog, getResultLog(handleParam, result, resultObj), e);
            /*
            throw new ServiceException(headerMap.get(Constant.REQ_ID), handlerContext.getServiceName(), ErrorCodeEnum.PROTOCOL_EXCEPTION.getCode(),
                    ErrorCodeEnum.PROTOCOL_EXCEPTION.getErrorMsg());*/
            throw new RuntimeException(e.getMessage());
        } finally {
            /*
            for (HandlerInterceptor handlerInterceptor : getHandlerInterceptors()) {
                handlerInterceptor.completeHandle(args, resultObj, exception, handlerContext);
            }*/
        }
        return resultObj;
    }

    private void log(boolean success, StopWatch stopWatch, String appNode, String url, Map<String, String> headers, String paramsLog, String resultLog) {
        stopWatch.stop();
        /*
        log.info("freeway rpc isSuccess: {}, remoteApp:{}, url: {}, totalTimeMillis: {}, localHost: {}, localApp: {}, headers: {}, params: {}, result: {}",
                success, appNode, url, stopWatch.getTotalTimeMillis(), Constant.LOCAL_IP, SwjConfig.getAppName(), headers, paramsLog, resultLog);

         */
        log.info("freeway rpc isSuccess: {}, remoteApp:{}, url: {}, totalTimeMillis: {}, localHost: {}, localApp: {}, headers: {}, params: {}, result: {}",
                success, appNode, url, stopWatch.getTotalTimeMillis(), SOAServiceCenter.localIP, SOAServiceCenter.applicationName, headers, paramsLog, resultLog);

    }

    private void logError(StopWatch stopWatch, String appNode, String url, Map<String, String> headers, String paramsLog, String resultLog, Exception e) {
        stopWatch.stop();
        /*
        log.error("freeway rpc isSuccess: {}, remoteApp:{}, url: {}, totalTimeMillis: {}, localHost: {}, localApp: {}, headers: {}, params: {}, result: {}",
                false, appNode, url, stopWatch.getTotalTimeMillis(), Constant.LOCAL_IP, SwjConfig.getAppName(), headers, paramsLog, resultLog, e);
                */

        log.error("freeway rpc isSuccess: {}, remoteApp:{}, url: {}, totalTimeMillis: {}, localHost: {}, localApp: {}, headers: {}, params: {}, result: {}",
                false, appNode, url, stopWatch.getTotalTimeMillis(), SOAServiceCenter.localIP, SOAServiceCenter.applicationName, headers, paramsLog, resultLog, e);

    }

    /*
    private String getParamLog(Object[] args) {
        Object object = null;
        if (args.length == 1) object = args[0];
        else if (args.length > 1) object = args;
        return JsonHelper.toJsonStringWithDesensitized(object, null, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.UseSingleQuotes);
    }*/

    private String getResultLog(HandleParam handleParam, String result, Object resultObj) {
        if (resultObj == null) return null;
        String resultLog = result;
        /*
        if (SensitiveUtils.isSensitive(handleParam.getMethodKey(), SensitiveUtils.RETURN, resultObj)) {
            resultLog = JsonHelper.toJsonStringWithDesensitized(resultObj, null, SerializerFeature.DisableCircularReferenceDetect,
                    SerializerFeature.UseSingleQuotes);
        }
        if (SwitchEnum.ALL == RpcLogConfig.getConfig().getResult()) {
            return resultLog;
        }*/
        return StringHelper.getHeadAndTail(resultLog, 300, 100);
    }

    /*
    private List<HandlerInterceptor> getHandlerInterceptors() {
        if (handlerInterceptors == null) {
            synchronized (lock) {
                if (handlerInterceptors == null) {
                    handlerInterceptors = new ArrayList<>();
                    try {
                        handlerInterceptors.addAll(SpringContextHelper.getApplicationContext()
                                .getBeansOfType(HandlerInterceptor.class)
                                .values()
                                .stream()
                                .sorted(Comparator.comparing(HandlerInterceptor::getOrder))
                                .collect(Collectors.toList()));
                    } catch (Exception ignored) {}
                }
            }
        }
        return handlerInterceptors;
    }*/

    private static Map<String,String> getHeaders(){
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        if(requestAttributes!=null) {
            HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();

            Enumeration<String> hList = request.getHeaderNames();
            Map<String,String> map = new HashMap<>();
            while (hList.hasMoreElements()){
                String header = hList.nextElement();
                map.put(header,request.getHeader(header));
            }

            return map;

        }
        return new HashMap<>();
    }



}
